from collections import namedtuple
from functools import partial, wraps

from flask import request, views, jsonify, g
from flask_login import login_required
from flask_restful import Resource, Api, reqparse, marshal, fields
from flask_restful.reqparse import Argument
from sqlalchemy.orm.attributes import InstrumentedAttribute
from werkzeug.datastructures import MultiDict

from app import db
from app.decorators import permission_required, global_error_handler
from app.utils import page2dict

CommonResponse = namedtuple('CommonResponse','code,message,data')


class parse_args(object):

    def __init__(self,arguments=None):
        self.arguments = arguments
        self.default_help ='{error_msg}'

    def __call__(self, f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            self.warp_args(request)
            return f(*args, **kwargs)
        return wrapper
    def warp_args(self,req):
        parser = reqparse.RequestParser(bundle_errors=True)

        if self.arguments:
            for arg in self.arguments:
                if not arg.help:
                    arg.help = self.default_help
                parser.add_argument(arg)
            request_args = parser.parse_args()


        else:
            request_args = self.source(req)
        g.args = request_args

    def source(self, request):
        values = {}
        for l in ('json', 'values',):
            value = getattr(request, l, None)
            if callable(value):
                value = value()
            if value is not None:
                values.update(value)
        return values

common_ok_fields = {
    'code': fields.String,
    'message': fields.String,
    'data': fields.Raw
}

class CommonResource(Resource):

    decorators = [global_error_handler]
    #decorators = [login_required,permission_required]

    @property
    def model(self):
        return self._model
    @model.setter
    def model(self,val):
        self._model = val

    def __init__(self,model):
        self.model = model

    default_paginate_args =[
        Argument(name='page' ,type = int,default=1),
        Argument(name='per_page' ,type = int,default=10),
    ]

    def get(self, id):

        if id is None:
            return self.get_list_handler(), 200
        else:
            return self.get_single_handler(id),200

    def get_single_handler(self,id):
        eintity = self.model.query.get(id)
        dv = marshal(eintity.dict(), self.get_fields, envelope='data')
        return dv

    def get_list_handler(self):
        return self.get_paginate()

    def get_paginate(self):
        paginate_args = self.paginate_args if hasattr(self,'paginate_args') else self.default_paginate_args
        parse_args(paginate_args).warp_args(request)
        per_page = g.args['per_page']
        page = g.args['page']

        page_obj = self.model.query.filter_by().paginate(page=page, per_page=per_page)
        json_obj = page2dict(page_obj, self.paginate_fields)
        return json_obj

    def post(self):
        parse_args(self.post_args).warp_args(request)

        print(g.args)
        args = g.args

        entity = self.model()
        for k, v in args.items():
            setattr(entity, k, v)
        self.post_entity_handler(entity)
        #print(entity)
        # create a new user
        db.session.add(entity)
        db.session.commit()
        data = {
            'data': entity.dict(('username','name','email')),
            'code': 201,
            'message': 'ok'
        }
        rs = marshal(data, common_ok_fields)

        return rs, 201

    def post_entity_handler(self,entity):
        pass
    def delete(self, id):
        entity = self.model.query.get(id)
        db.session.delete(entity)
        db.session.commit()
        # delete a single entity
        data = {
            'code': 204,
            'message': 'ok',
            'data': ''
        }
        rs = marshal(data, common_ok_fields)
        return rs, 200
        # return rs,204

    def put(self, id):

        #args = parser.parse_args()

        #entity = self.model.query.get(id)
        parse_args(self.put_args).warp_args(request)
        args = g.args
        dic = self.create_update_dic(args)
        db.session.query(self.model).filter(self.model.id == id).update(dic,
                                                         synchronize_session=False)
        db.session.commit()
        data = {
            'code': 201,
            'message': 'ok',
            'data': ''
        }
        rs = marshal(data, common_ok_fields)

        return rs,201
        # update a single user
        pass

    def create_update_dic(self, args):
        dic = {}
        if not args:
            pass
        for c in args:
            attr = getattr(self.model, c, None)
            if attr and issubclass(type(attr), (InstrumentedAttribute,)):
                dic[attr] = args[c]
        return dic

    pass


class CommonApi(Api):


    def __init__(self, app=None):

        # if app is not None:
        #     self.init_app(app)
        super().__init__(app)

    @property
    def app(self):
        return self._app

    @app.setter
    def app(self,val):
        self._app = val

    def init_app(self,app):
        self.app = app


    def register_resource(self, resource, url, **kwargs):
        endpoint = kwargs.pop('endpoint', None) or resource.__name__.lower()
        pk = kwargs.pop('pk', None) or 'id'
        pk_type = kwargs.pop('pk_type', None) or 'int'

        self.add_resource(resource,url,methods=['GET',],endpoint="%s_r"%endpoint,defaults={pk:None}, **kwargs)
        self.add_resource(resource,url,methods=['POST',],endpoint="%s_c"%endpoint, **kwargs)
        self.add_resource(resource,
                         '%s<%s:%s>' % (url, pk_type, pk),endpoint="%s_rud"%endpoint, **kwargs)
